var Util = {

	/* Constants */
	invalidArrayIndex:  -1,
	unset:              '',

	/* Variables */
	currentId:       1970,
	onLoadCallbacks: [],
	
	attempt: function( funk ) {
		try { return funk(); } catch( whatever ) {}
	},

	id: function( id ) {
		return document.getElementById( id );
	},

	debug: false,

	utilConsoleId: 'utilConsoleId',

	valu: function( id, v ) {
		var element = Util.id( id );
		if ( element ) {
			if ( v ) {
				element.value = v;
			}
			v = element.value;
		} else {
			v = Util.unset;
		}
		return v;
	},

	tag: function( name ) {
		 return document.getElementsByTagName( name );
	},

	each: function( a, funk ) {
		var r = [];
		for ( var i = 0 ; i < a.length ; i++ ) {
			r.push( funk( a[ i ] ) );
		}
		return r;
	},

	move: function( id, x, y, z ) {
		Utils.moveElement( Utils.id( id ), x, y, z );
	},

	moveElement: function( element, x, y, z ) {
		try {
			z = z ? z : 0;
			element.style.position = 'absolute';
			element.style.top = y + 'px';
			element.style.left = x + 'px';
			element.style.zIndex = z;
		} catch ( a_cold ) {
		}
	},

	// firefox only...
	parseToDocument: function( xml ) {
		return ( new DOMParser() ).parseFromString( 
			xml
			, "text/xml"
		);
	},

	show: function( element ) {
		element.style.display = 'block';
	},
		  
	hide: function( element ) {
		element.style.display = 'none';
	},

	hideShowElement: function( element ) {
		try {
			if ( 'none' == element.style.display ) {
				Util.show( element );
			} else {
				Util.hide( element );
			}
		} catch( a_cold ) {
		}
	},

	hideShow: function( id ) {
		try {
			Util.hideShowElement( Util.id( id ) );
		} catch( a_cold ) {
		}
	},

	trim: function( s ) {
		return s.replace( /^\s+/, '' ).replace( /\s+$/, '' );
	},

	load: function( url, id ) {
		Util.submitRequest( 
			Util.requestFactory()
			, url
			, function( x ) { Util.id( id ).innerHTML = x; }
			, 'text'
		);
	},

	moveTo: function( node, x, y ) {
		node.style.position = 'absolute';
		node.style.left = x + 'px';
		node.style.top = y + 'px';
	},

	setSize: function( node, w, h ) {
		node.style.width = w + 'px';
		node.style.height = h + 'px';
	},

	position: function( node, x, y, w, h ) {
		Util.moveTo( node, x, y );
		Util.setSize( node, w, h );
	},

	cssSprite: function( node, image_url, w, h, x, y ) {
		Util.setSize( node, w, h );
		node.style.background = 'url( ' + image_url + ' )';
		node.style.backgroundPosition = '-' + x + 'px -' + y + 'px';
	},

	mousePosition: function( event, parent ) {
		var pos_x = event.offsetX?(event.offsetX):event.pageX-parent.offsetLeft;
		var pos_y = event.offsetY?(event.offsetY):event.pageY-parent.offsetTop;
		
		var x = pos_x;
		var y = pos_y;
		x += parent.scrollLeft;
		y += parent.scrollTop;
		return new Point2d( x, y );
	},

	selectron: function( node, criteria, action ) {
		try {
			if ( criteria( node ) ) {
				action( node );
			}
		} catch( exception ) {
			//alert( exception );
			//return;
		}
		if ( node.hasChildNodes() ) {
			var kids = node.childNodes;
			for ( var i = 0 ; i < kids.length ; i++ ) {
				Util.selectron( kids[ i ], criteria, action );
			}
		}
	},

	/* there are a lot of ways to hide elements */
	completelyVisible: function( element ) {
		Util.opacity( element, 1 );
		Util.show( element );
		Util.visible( element );
	},

	opacity: function( element, value ) {
		if ( value ) {
			element.style.opacity = value;
		}
		return element.style.opacity;
	},

	invisible: function( element ) {
		element.style.visibility = 'hidden';
	},

	visible: function( element ) {
		element.style.visibility = 'visible';
	},	

	toggleVisibility: function( element ) {
		try {
			if ( 'hidden' == element.style.visibility ) {
				Util.visible( element );
			} else {
				Util.invisible( element );
			}
		} catch( a_cold ) {
		}
	},

	allKids: function( node, action ) {
		if ( node.hasChildNodes() ) {
			var kids = node.childNodes;
			for ( var i = 0 ; i < kids.length ; i++ ) {
				try {
					action( kids[ i ] );
				} catch ( e ) {
					//alert( e );
				}
			}
		}
	},

	kids: function( node, accept, action ) {
		if ( node.hasChildNodes() ) {
			var kids = node.childNodes;
			for ( var i = 0 ; i < kids.length ; i++ ) {
				try {
					if ( accept( kids[ i ] ) ) {
						action( kids[ i ] );
					}
				} catch ( e ) {
					//alert( e );
				}
			}
		}
	},

	hasAttribute: function( node, name, value ) {
		return node.getAttribute( 'name' ) == value;
	},

	hasAttributeThatContains: function( node, name, value ) {
		return -1 != node.getAttribute( 'name' ).indexOf( value );
	},

	stringContainsWord: function( string, word ) {
		return (
			( null == string || null == word ) 
			? false 
			: (
				string == word
				|| Util.stringContains(
					  ( ' ' + string.replace( /\s/g, ' ' ) + ' ' )
					, ( ' ' + word + ' ' )
				)
			)
		);
	},

	hasAttributeThatContainsWord: function( node, name, value ) {
		return (
			node.getAttribute
			? Util.stringContainsWord( node.getAttribute( name ), value )
			: false 
		);
	},

	hasClass: function( node, value ) {
		return Util.hasAttributeThatContainsWord( node, 'class', value )
		|| Util.hasAttributeThatContainsWord( node, 'className', value ); // IE is dumb
	},

	acceptAllNode: function( node ) {
		return true;
	},

	/* 
		make sure tagName is all upper case or suffer! 
		note:  'LI' will match a node.nodeName of 'XHTML:LI' or '.*:LI'
	 */
	isTag: function( node, tagName ) {
		return node.nodeName == tagName || node.nodeName.replace( /.*:/, '' ) == tagName;
	},

	walkNodes: function( nodes, accept, action ) {
		for ( var i = 0 ; i < nodes.length ; i++ ) {
			Util.walkNode( nodes[ i ], accept, action );
		}
	},

	walkNode: function( node, accept, action ) {
		try {
			if ( accept( node ) ) {
				action( node );
			}
		} catch ( e ) {}
		if ( node.hasChildNodes() ) {
			Util.walkNodes( node.childNodes, accept, action );
		}
	},

	walkDom: function( accept, action ) {
		Util.walkNode( document, accept, action );
	},

	/* value should be from 0.0 to 1.0 */
	opaque: function( element, value ) {
		element.style.opacity = value;
	}, 

	/* from http://www.quirksmode.org/js/findpos.html */
	elementPosition: function( element ) {
		var left = 0;
		var top = 0;
		if ( element.offsetParent ) {
			do {
				left += element.offsetLeft;
				top  += element.offsetTop;
			} while ( element = element.offsetParent );
		}
		return { x : left, y : top };
  	},				  

	elementSize: function( element ) {
		return { 
			  w : Util.pxToInt( Util.elementWidth( element ) )
			, h : Util.pxToInt( Util.elementHeight( element ) )
		};
	},

	attribute: function( element, name, value ) {
		if ( 3 == arguments.length ) { 
			element.setAttribute( name, value );
		} 
		var v = element.getAttribute( name );
		if ( !v ) {
			v = '';
		}
		return v;
	},

	stringContains: function( haystack, needle ) {
		return ( haystack == needle ) || ( -1 != haystack.indexOf( needle ) );
	},

	attributeContains: function( element, name, value ) {
		return Util.stringContains( Util.attribute( element, name ), value );
	},

	nextId: function() {
		return Util.currentId++;
	},

	elementHeight: function( element ) {
		return (
			0 
			|| element.style.height
			|| element.offsetHeight 
			|| element.style.pixelHeight 
		);
	},

	elementWidth: function( element ) {
		return (
			0 
			|| element.style.width
			|| element.offsetWidth 
			|| element.style.pixelWidth 
		);
	},

	removeNode: function( node ) {
		node.parentNode.removeChild( node );
	},

	kill: function( node ) {
		Util.removeNode( node.parentNode );
	},

	copyBean : function( bean ) {
		var copy = {};
		for ( var name in bean ) {
			copy[ name ] = bean[ name ];
		}
		return copy;
	},

	newNode: function( type, parameters ) {
		var node = document.createElement( type );
		if ( parameters ) {
			for ( var name in parameters ) {
				if ( 'className' == name ) {
					node.setAttribute( 'class', parameters[ name ] ); // IE is dumb
				} else {
					if ( 'class' == name ) {
						node.setAttribute( 'className', parameters[ name ] ); // IE is dumb
					}
				}
				node.setAttribute( name, parameters[ name ] );
			}
		}
		return node;
	},

	newTxt: function( txt ) {
		return document.createTextNode( txt );
	},

	newText: function( text ) {
		return document.createTextNode( text );
	},

	find: function( needle, haystack ) {
		var index = Util.invalidArrayIndex;
		for ( var i = 0 ; i < haystack.length ; i++ ) {
			if ( needle == haystack[ i ] ) {
				index = i;
				break;
			}
		}
		return index;
	},

	push: function( a, e ) {
		a.push( e );
		return e;
	},

	removeFromArray: function( all, unwanted ) {
/*
		var index = all.indexOf( unwanted );
		if ( -1 != index ) {
			all.splice( index, 1 );
		}
		return all;
*/
		var keep = [];
		for ( var i = 0 ; i < all.length ; i++ ) {
			if ( all[ i ] != unwanted ) {
				keep.push( all[ i ] );
			}
		}
		return keep;
	},

	pxToInt: function( s ) {
		var v = Math.floor( ( new String( s ) ).replace( /\s*px$/, '' ) );
		return v ? v : 0;
	},

	pullText: function( node ) {
		var text = '';
		Util.walkNode(
			node
			, function( node ) { return node.type = 'TXT'; }
			, function( node ) { 
				if ( node.nodeValue ) {
					text += ( text ? ' ' : '' ) + node.nodeValue; 
				}
			}
		);
		return text;
	},

	addOnLoadCallback: function( callback ) {
		Util.onLoadCallbacks.push( callback );
		window.onload = Util.onLoad;
	},

	onLoad: function() {
		for ( var i = 0 ; i < Util.onLoadCallbacks.length ; i++ ) {
			Util.onLoadCallbacks[ i ]();
		}
	},

	/* http://www.denisvlasov.net/129/javascript-prependchild/ 
	   this bs does not work...
	 */
	prependChild: function( parent, child ) {
		parent.appendChild( child, parent.firstChild );
	},

	console: function() {
		var console = Util.id( Util.utilConsoleId );
		if ( !console ) {
			var stain = Util.newNode( 'div' );
			console = Util.newNode( 'div' , { id:Util.utilConsoleId } );
			var btn = Util.newNode( 'button' );
			btn.appendChild( Util.newText( '!reset console ..........................' ) );
			btn.onclick = function() { Util.console().innerHTML = ''; };
			stain.appendChild( console );
			stain.appendChild( Util.newNode( 'hr' ) );
			stain.appendChild( btn );
			document.body.appendChild( stain );
		}
		return console;
	},

	log: function( text ) {
		if ( Util.debug ) {
			//var div = Util.newNode( 'div' );
			//div.appendChild( Util.newText( new Date() + ' |' + text ) );
			//Util.prependChild( Util.console(), div );
			var konsole = Util.console();
			konsole.innerHTML = '<div>' + text + '</div>' + konsole.innerHTML;
		}
	},

	randomArrayEntry: function( arr ) {
		return arr[ Math.floor( Math.random() * arr.length ) ];
	},

	classy: function( element, value ) {
		element.setAttribute( 'class', value );
		element.setAttribute( 'className', value ); // IE is dumb
	},

	jsInclude: function( src ) {
		return Util.newNode( 
			'script' , { type:'text/javascript' , src:src }
		);
	},

	/* 
	   PRECONDITION: document must have a <HEAD/>
	   also... notice how this splits on spaces... 
	   don't be a fcktrd and put spaces in your filenames
	*/
	includeJs: function( path, junk ) {
		var head = document.getElementsByTagName( 'head' )[ 0 ];
		Util.each( 
			junk.split( ' ' )
			, function includeJsEach( js ) {
				head.appendChild( Util.jsInclude( path + '/' + js + '.js' ) );
			}
		);
	},

	cssInclude: function( src ) {
		return Util.newNode( 'link' , {rel:'stylesheet',type:'text/css',href:src } );
	},

	includeCss: function( path, junk ) {
		var head = document.getElementsByTagName( 'head' )[ 0 ];
		Util.each( 
			junk.split( ' ' )
			, function includeCssEach( css ) {
				head.appendChild( Util.cssInclude( path + '/' + css + '.css' ) );
			}
		);
	},

	/* careful: this is very silly, something like 
	   http://af-design.com/services/javascript/uuid/uuid.js 
	   is neater, but...
	*/
	uuid: function() {
		return Util.nextId() + ( 
			+ 'u' + Math.random()
			+ 'u' + Math.random()
			+ 'i' + Math.random()
			+ 'd' + Math.random()
			+ '.' + Math.random()
		);
	},

	keys: function( a ) {
		var k = [];
		for ( var key in a ) k.push( key );
		return k;
	},

	isEmpty: function( a ) {
		var none = true;
		for ( var key in a ) {
			none = false;
			break;
		}
		return none;
	},

	insertIntoLocation: function( a, element, bigger ) {
		var min = 0;
		var max = a.length;
		var where = 0;
		while ( true ) {
			where = Math.floor( min + ( max - min ) / 2 );
			if ( a.length == where ) {
				break;  // end of the array
			}
			if ( bigger( element, a[ where ] ) ) {
				min = where + 1;
			} else {
				if ( 0 == where || bigger( element, a[ where - 1 ] ) ) {
					break;
				} else {
					max = where;
				}
			}
		} 
		return where;
	},

	insertIntoOrdered: function( a, element, evalFunction ) {
		Util.debug = true;
		a.splice( Util.insertIntoLocation( a, element, evalFunction), 0, element );
		return a;
	},

	insertIntoAscending: function( a, element ) {
		return Util.insertIntoOrdered( a, element, function( a, b ) { return a < b; } );
	},

	insertIntoDescending: function( a, element ) {
		return Util.insertIntoOrdered( a, element, function( a, b ) { return a > b; } );
	},

	noop:  function() {}
};
